//===============================================================================
// Microsoft Architecture Strategy Team
// LitwareHR - SaaS Sample Application
//===============================================================================
// Copyright  Microsoft Corporation.  All rights reserved.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//===============================================================================
// The example companies, organizations, products, domain names,
// e-mail addresses, logos, people, places, and events depicted
// herein are fictitious.  No association with any real company,
// organization, product, domain name, email address, logo, person,
// places, or events is intended or should be inferred.
//===============================================================================

using System;
using System.Collections.Generic;
using System.Text;
using System.IdentityModel.Policy;
using System.Collections.ObjectModel;
using System.IdentityModel.Claims;
using System.IdentityModel.Tokens;
using Shp.Runtime.Services;
using Shp.Runtime.Contract;

namespace Shp.Security.BrokeredReceiver
{
    /// <summary>
    /// Base class for adding authorization access claims (actions) for the current user.
    /// </summary>
    public abstract class AuthorizationSecurityTokenServicePolicy : IAuthorizationPolicy
    {
        private string id;

        /// <summary>
        /// Initializes a new instance of the <see cref="T:AuthorizationSecurityTokenServicePolicy"/> class.
        /// </summary>
        public AuthorizationSecurityTokenServicePolicy()
        {
            id = Guid.NewGuid().ToString();
        }

        #region IAuthorizationPolicy Members

        /// <summary>
        /// Evaluates whether a user meets the requirements for this authorization policy.
        /// </summary>
        /// <param name="evaluationContext">An <see cref="T:System.IdentityModel.Policy.EvaluationContext"></see> that contains the claim set that the authorization policy evaluates.</param>
        /// <param name="state">A <see cref="T:System.Object"></see>, passed by reference that represents the custom state for this authorization policy.</param>
        /// <returns>
        /// false if the <see cref="M:System.IdentityModel.Policy.IAuthorizationPolicy.Evaluate(System.IdentityModel.Policy.EvaluationContext,System.Object@)"></see> method for this authorization policy must be called if additional claims are added by other authorization policies to evaluationContext; otherwise, true to state no additional evaluation is required by this authorization policy.
        /// </returns>
        public bool Evaluate(EvaluationContext evaluationContext, ref object state)
        {
            // check if this context was updated for this user
            if (state == null)
            {
                // Create an empty list of Claims
                IList<Claim> claims = new List<Claim>();
                // Add list of actions the user can perform
                string tmp = GetUserFromClaimSets(evaluationContext.ClaimSets);
                Upn upn = new Upn(tmp);
                foreach (string action in GetActionsForUser(upn))
                {
                    claims.Add(new Claim(ClaimTypes.AuthorizationDecision, action, Rights.PossessProperty));
                }
                // Add the user name that came as UPN
                claims.Add(new Claim(ClaimTypes.Name, upn.ToString(), Rights.PossessProperty));
                // Add tenant alias and id
                claims.Add(new Claim(Constants.Multitenancy.TenantAlias, upn.Domain, Rights.PossessProperty));
                string tenantId = AuthorizationLogic.GetTenantIdByAlias(upn.Domain).ToString();
                claims.Add(new Claim(Constants.Multitenancy.TenantId, tenantId, Rights.PossessProperty));

                if (claims.Count > 0)
                {
                    // Add claims to the evaluation context    
                    evaluationContext.AddClaimSet(this, new DefaultClaimSet(this.Issuer, claims));
                }
                state = "updated";
            }
            return true;
        }

        /// <summary>
        /// Gets a claim set that represents the issuer of the authorization policy.
        /// </summary>
        /// <value></value>
        /// <returns>A <see cref="T:System.IdentityModel.Claims.ClaimSet"></see> that represents the issuer of the authorization policy.</returns>
        public ClaimSet Issuer
        {
            get { return ClaimSet.System; }
        }

        #endregion

        #region IAuthorizationComponent Members

        /// <summary>
        /// Gets a string that identifies this authorization component.
        /// </summary>
        /// <value></value>
        /// <returns>A string that identifies this authorization component.</returns>
        public string Id
        {
            get { return id; }
        }

        #endregion

        /// <summary>
        /// Gets the actions for user.
        /// </summary>
        /// <param name="user">The user.</param>
        /// <returns></returns>
        protected abstract string[] GetActionsForUser(Upn upn);

        private string GetUserFromClaimSets(ReadOnlyCollection<ClaimSet> claimSets)
        {
            foreach (ClaimSet claimSet in claimSets)
            {
                // Copy Name claims from the incoming credentials into the set of claims we're going to issue
                IEnumerable<Claim> authClaims = claimSet.FindClaims(ClaimTypes.Authentication, Rights.PossessProperty);
                if (authClaims != null)
                {
                    IEnumerator<Claim> authClaimsEnum = authClaims.GetEnumerator();
                    if (authClaimsEnum.MoveNext())
                    {
                        return authClaimsEnum.Current.Resource.ToString();
                    }
                }
            }
            throw new SecurityTokenException("User not authenticated");
        }
    }
}
